package priv.wangy.micro.common.utils;

import jodd.io.FileNameUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.NullInputStream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import priv.wangy.micro.common.utils.secret.AesUtil;
import priv.wangy.micro.common.thread.ThreadPool;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.zip.*;

/**
 * 文件压缩工具
 *
 * @author 张嘉琪
 * @since 2022-10-19
 */
@Slf4j
public class ZipUtil {

    /**
     * 删除文件
     *
     * @param fileName 文件名称，全路径
     */
    public static void delete(String fileName) throws Exception {
        File target = new File(fileName);
        if (!target.exists()) {
            return;
        }
        if (target.isDirectory()) {
            File[] files = target.listFiles();
            if (Objects.nonNull(files) && ArrayUtils.isNotEmpty(files)) {
                for (File file : files) {
                    delete(file.getAbsolutePath());
                }
            }
        }
        System.gc();
        if (!target.delete()) {
            throw new Exception("文件[" + target.getName() + "]删除失败");
        }
    }

    /**
     * 压缩文件
     *
     * @param zos       压缩流
     * @param fileName  待压缩的文件名，全路径
     * @param zipFolder 压缩文件保存目录名称
     */
    private static void zip(ZipOutputStream zos, String fileName, String zipFolder) throws IOException {
        File target = new File(fileName);
        if (target.isFile()) {
            zos.putNextEntry(new ZipEntry(zipFolder + target.getName()));
            IOUtils.copy(new FileInputStream(target.getAbsolutePath()), zos);
            zos.flush();
            zos.closeEntry();
        } else {
            File[] files = target.listFiles();
            if (Objects.nonNull(files) && ArrayUtils.isNotEmpty(files)) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        zip(zos, FileNameUtil.normalizeNoEndSeparator(file.getAbsolutePath()),
                                FileNameUtil.normalizeNoEndSeparator(zipFolder + file.getName()) + "/");
                    } else {
                        FileInputStream fis = new FileInputStream(file.getAbsolutePath());
                        zos.putNextEntry(new ZipEntry(zipFolder + file.getName()));
                        IOUtils.copy(fis, zos);
                        fis.close();
                        zos.flush();
                        zos.closeEntry();
                    }
                }
            } else {
                zos.putNextEntry(new ZipEntry(zipFolder + "/"));
                zos.closeEntry();
            }
        }
    }

    /**
     * 压缩文件夹
     *
     * @param zipFileName 压缩后的文件名，全路径
     * @param sourceFile  待压缩的文件或文件夹
     * @param zipFolder   压缩文件保存目录名称
     */
    public static void zip(String zipFileName, String sourceFile, String zipFolder) throws Exception {
        ZipOutputStream zos = null;
        try {
            File zipFile = new File(zipFileName);
            FileOutputStream fos = new FileOutputStream(zipFile);
            zos = new ZipOutputStream(fos);
            if (StringUtils.isNotBlank(zipFolder)) {
                zipFolder = FileNameUtil.normalizeNoEndSeparator(zipFolder) + "/";
            }
            zip(zos, sourceFile, zipFolder);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        } finally {
            IOUtils.close(zos);
        }
    }

    /**
     * 压缩文件
     *
     * @param zipFileName 压缩后的文件名，全路径
     * @param sourceFile  待压缩的文件或文件夹
     */
    public static void zip(String zipFileName, String sourceFile) throws Exception {
        File file = new File(zipFileName);
        File dir = file.getParentFile();
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new Exception("记录文件目录创建失败");
            }
        }
        File source = new File(sourceFile);
        zip(zipFileName, sourceFile, StringUtils.EMPTY);
    }

    /**
     * 压缩并删除源文件
     *
     * @param zipFileName 压缩后的文件名，全路径
     * @param sourceFile  待压缩的文件或文件夹
     */
    public static void zipAndDelete(String zipFileName, String sourceFile) throws Exception {
        zip(zipFileName, sourceFile);
        delete(sourceFile);
    }

    /**
     * 解压缩文件
     *
     * @param destFile 目标文件
     * @param zis      压缩流
     */
    private static void unzip(File destFile, ZipInputStream zis) throws Exception {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            String fileName = destFile.getPath() + File.separator + entry.getName();
            File file = new File(fileName);
            if (entry.isDirectory()) {
                if (!file.mkdirs()) {
                    throw new Exception("文件目录创建失败");
                }
            } else {
                File dir = file.getParentFile();
                if (!dir.exists()) {
                    if (!dir.mkdirs()) {
                        throw new Exception("导出文件目录创建失败");
                    }
                }
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                int count;
                byte[] data = new byte[1024];
                while ((count = zis.read(data, 0, 1024)) != -1) {
                    bos.write(data, 0, count);
                }
                bos.close();
            }
            zis.closeEntry();
        }
    }

    /**
     * 解压缩文件
     *
     * @param srcFile  原文件
     * @param destFile 目标文件
     */
    public static void unzip(File srcFile, File destFile) throws Exception {
        CheckedInputStream cis = new CheckedInputStream(new FileInputStream(srcFile), new CRC32());
        ZipInputStream zis = new ZipInputStream(cis, StandardCharsets.UTF_8);
        unzip(destFile, zis);
        zis.close();
    }

    /**
     * 解压缩文件
     *
     * @param srcPath  原文件名，全路径
     * @param destPath 目标文件名，全路径
     */
    public static void unzip(String srcPath, String destPath) throws Exception {
        unzip(new File(srcPath), new File(destPath));
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class FileBo {

        /**
         * 文件名
         */
        private String fileName;

        /**
         * 文件路径
         */
        private String filePath;
    }

    private static boolean mkdir(String fileName) {
        File file = new File(fileName);
        File dir = file.getParentFile();
        if (Objects.nonNull(dir) && !dir.exists()) {
            return dir.mkdirs();
        } else if (dir.exists()) {
            return true;
        }
        return false;
    }

    /**
     * 压缩文件
     * 当FileBo.fileName不为空时，压缩包内的文件名称将被替换
     *
     * @param fileList    待压缩的文件列表
     * @param zipFileName 压缩后的文件名
     * @author 张嘉琪
     * @since 2023-05-08
     */
    public static void compressFileList(List<FileBo> fileList, String zipFileName) {
        if (!mkdir(zipFileName)) {
            log.error("创建文件失败");
            return;
        }
        ParallelScatterZipCreator creator = new ParallelScatterZipCreator(ThreadPool.getPool());
        try (
                OutputStream os = Files.newOutputStream(Paths.get(zipFileName));
                ZipArchiveOutputStream zos = new ZipArchiveOutputStream(os)
        ) {
            zos.setEncoding("UTF-8");
            zos.setUseZip64(Zip64Mode.AsNeeded);
            for (FileBo fileBo : fileList) {
                if (!org.springframework.util.StringUtils.hasLength(fileBo.filePath)) {
                    continue;
                }
                File file = new File(fileBo.getFilePath());
                final InputStreamSupplier supplier = () -> {
                    if (fileBo.getFilePath().endsWith(".mw")) {
                        try (InputStream fis = new FileInputStream(file);
                             ByteArrayOutputStream fos = new ByteArrayOutputStream()) {
                            if (AesUtil.decryptFile(fis, fos)) {
                                return new ByteArrayInputStream(fos.toByteArray());
                            }
                        } catch (IOException e) {
                            log.error(e.getMessage(), e);
                        }
                        return new NullInputStream(0);
                    } else {
                        try {
                            return new FileInputStream(file);
                        } catch (FileNotFoundException e) {
                            log.error(e.getMessage(), e);
                            return new NullInputStream(0);
                        }
                    }
                };
                String fileName = org.springframework.util.StringUtils.hasLength(fileBo.getFileName()) ? fileBo.getFileName() : file.getName();
                ZipArchiveEntry entry = new ZipArchiveEntry(fileName);
                entry.setMethod(ZipArchiveEntry.DEFLATED);
                entry.setSize(file.length());
                creator.addArchiveEntry(entry, supplier);
            }
            creator.writeTo(zos);
        } catch (IOException | ExecutionException | InterruptedException e) {
            log.error(e.getMessage(), e);
        }
    }
}
